package net.xiaohanlin.smartmetric.monitor.system;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.hyperic.sigar.CpuInfo;
import org.hyperic.sigar.CpuPerc;
import org.hyperic.sigar.SigarException;

import com.codahale.metrics.Gauge;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricSet;
import com.codahale.metrics.RatioGauge;

import net.xiaohanlin.smartmetric.metricname.CpuMetricNameEnum;

public class CpuGaugeSet extends AbstractSystemMetric implements MetricSet {

	private static final long HACK_DELAY_MILLIS = 1000;
	protected CpuInfo cpuInfo = null;

	public CpuGaugeSet() {
		this.cpuInfo = getCpuInfo();
	}

	@Override
	public Map<String, Metric> getMetrics() {
		final Map<String, Metric> gauges = new HashMap<String, Metric>();
		gauges.put(getMetricName(CpuMetricNameEnum.cpu_logical_cores), measureTotalCores());
		gauges.put(getMetricName(CpuMetricNameEnum.cpu_physical_count), measurePhysicalCores());
		gauges.put(getMetricName(CpuMetricNameEnum.cpu_time_user_percent), measureCpuTimeUserPercent());
		gauges.put(getMetricName(CpuMetricNameEnum.cpu_time_sys_percent), measureCpuTimeSysPercent());

		gauges.put(getMetricName(CpuMetricNameEnum.load_average_1_min), new RatioGauge() {

			@Override
			protected Ratio getRatio() {
				try {
					return Ratio.of(sigar.getLoadAverage()[0], 1.0);
				} catch (SigarException e) {
					return Ratio.of(0, 1.0);
				}
			}
		});

		gauges.put(getMetricName(CpuMetricNameEnum.load_average_5_min), new RatioGauge() {

			@Override
			protected Ratio getRatio() {
				try {
					return Ratio.of(sigar.getLoadAverage()[1], 1.0);
				} catch (SigarException e) {
					return Ratio.of(0, 1.0);
				}
			}
		});

		gauges.put(getMetricName(CpuMetricNameEnum.load_average_15_min), new RatioGauge() {

			@Override
			protected Ratio getRatio() {
				try {
					return Ratio.of(sigar.getLoadAverage()[2], 1.0);
				} catch (SigarException e) {
					return Ratio.of(0, 1.0);
				}
			}
		});

		return gauges;
	}

	protected Metric measureTotalCores() {
		return new Gauge<Integer>() {

			public Integer getValue() {
				return totalCoreCount();
			}
		};
	}

	protected Metric measureCpuTimeSysPercent() {
		return new RatioGauge() {

			@Override
			protected Ratio getRatio() {
				return Ratio.of(getNumerator(), 1.0);
			}

			private double getNumerator() {
				List<CpuTime> cpus = cpus();
				double userTime = 0.0;
				for (CpuTime cpu : cpus) {
					userTime += cpu.sys();
				}
				return userTime;
			}
		};
	}

	protected Metric measureCpuTimeUserPercent() {
		return new RatioGauge() {

			@Override
			protected Ratio getRatio() {
				return Ratio.of(getNumerator(), 1.0);
			}

			private double getNumerator() {
				List<CpuTime> cpus = cpus();
				double userTime = 0.0;
				for (CpuTime cpu : cpus) {
					userTime += cpu.user();
				}
				return userTime;
			}
		};
	}

	private CpuPerc[] cpuPercList() {
		CpuPerc[] cpus = null;
		try {
			cpus = sigar.getCpuPercList();
		} catch (SigarException e) {
			// give up
		}
		if (cpus == null || cpus.length == 0) {
			return null;
		}
		return cpus;
	}

	public List<CpuTime> cpus() {
		List<CpuTime> result = new ArrayList<CpuTime>();
		CpuPerc[] cpus = cpuPercList();
		if (cpus == null) {
			return result;
		}

		if (Double.isNaN(cpus[0].getIdle())) {
			/*
			 * XXX: Hacky workaround for strange Sigar behaviour. If you call
			 * sigar.getCpuPerfList() too often(?), it returns a steaming pile
			 * of NaNs.
			 *
			 * See suspicious code here:
			 * https://github.com/hyperic/sigar/blob/master/bindings/java/src/
			 * org/hyperic/sigar/Sigar.java#L345-348
			 *
			 */
			try {
				Thread.sleep(HACK_DELAY_MILLIS);
			} catch (InterruptedException e) {
				Thread.currentThread().interrupt();
				return result;
			}
			cpus = cpuPercList();
			if (cpus == null) {
				return result;
			}
		}
		for (CpuPerc cp : cpus) {
			result.add(CpuTime.fromSigarBean(cp));
		}
		return result;
	}

	protected Metric measurePhysicalCores() {
		return new Gauge<Integer>() {

			public Integer getValue() {
				return physicalCpuCount();
			}
		};
	}

	public int physicalCpuCount() {
		if (cpuInfo == null) {
			return -1;
		}
		return cpuInfo.getTotalSockets();
	}

	protected CpuInfo getCpuInfo() {
		try {
			CpuInfo[] infos = sigar.getCpuInfoList();
			if (infos == null || infos.length == 0) {
				return null;
			}
			return infos[0];
		} catch (SigarException e) {
			// give up
			return null;
		}
	}

	public int totalCoreCount() {
		if (cpuInfo == null) {
			return -1;
		}
		return cpuInfo.getTotalCores();
	}

	public static final class CpuTime {

		private final double user;
		private final double sys;
		private final double nice;
		private final double waiting;
		private final double idle;
		private final double irq;

		public CpuTime( //
				double user, double sys, //
				double nice, double waiting, //
				double idle, double irq) {
			this.user = user;
			this.sys = sys;
			this.nice = nice;
			this.waiting = waiting;
			this.idle = idle;
			this.irq = irq;
		}

		public static CpuTime fromSigarBean(CpuPerc cp) {
			return new CpuTime( //
					cp.getUser(), cp.getSys(), //
					cp.getNice(), cp.getWait(), //
					cp.getIdle(), cp.getIrq());
		}

		public double user() {
			return user;
		}

		public double sys() {
			return sys;
		}

		public double nice() {
			return nice;
		}

		public double waiting() {
			return waiting;
		}

		public double idle() {
			return idle;
		}

		public double irq() {
			return irq;
		}
	}
}
